package JsonMarshal

import (
	"reflect"
	"strconv"
	"math"
	"fmt"
)

const startDetectingCyclesAfter = 1000

func invalidValueEncoder(e *encodeState,v reflect.Value) {
	e.WriteString("null")
}

func unsupportedTypeEncoder(e *encodeState, v reflect.Value) {
	e.error(&UnsupportedTypeError{v.Type()})
}

func interfaceEncoder(e *encodeState, v reflect.Value) {
	if v.IsNil() {
		e.WriteString("null")
		return
	}
	e.marshal(v.Elem())
}

func basicEncoder(e *encodeState, v reflect.Value) {
	var b []byte
	switch v.Type().Kind() {
	case reflect.String:
		b = []byte("\"" + v.String() + "\"")
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		b = strconv.AppendInt([]byte(nil), v.Int(), 10)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		b = strconv.AppendUint([]byte(nil), v.Uint(), 10)
	case reflect.Float32, reflect.Float64:
		bits := 32
		if v.Type().Kind() == reflect.Float64 {
			bits = 64
		}
		f := v.Float()
		if math.IsInf(f, 0) || math.IsNaN(f) {
			e.error(&UnsupportedValueError{v, strconv.FormatFloat(f, 'g', -1, int(bits))})
		}

		// Convert as if by ES6 number to string conversion.
		// This matches most other JSON generators.
		// See golang.org/issue/6384 and golang.org/issue/14135.
		// Like fmt %g, but the exponent cutoffs are different
		// and exponents themselves are not padded to two digits.
		abs := math.Abs(f)
		fmt := byte('f')
		// Note: Must use float32 comparisons for underlying float32 value to get precise cutoffs right.
		if abs != 0 {
			if bits == 64 && (abs < 1e-6 || abs >= 1e21) || bits == 32 && (float32(abs) < 1e-6 || float32(abs) >= 1e21) {
				fmt = 'e'
			}
		}
		b = strconv.AppendFloat(b, f, fmt, -1, int(bits))
		if fmt == 'e' {
			// clean up e-09 to e-9
			n := len(b)
			if n >= 4 && b[n-4] == 'e' && b[n-3] == '-' && b[n-2] == '0' {
				b[n-2] = b[n-1]
				b = b[:n-1]
			}
		}
	case reflect.Bool:
		b = strconv.AppendBool([]byte(nil), v.Bool())
	}
	e.Write(b)
}

type ptrEncoder struct {
	elemEnc encoderFunc
}

func (pe ptrEncoder) encode(e *encodeState, v reflect.Value) {
	if v.IsNil() {
		e.WriteString("null")
		return
	}
	if e.ptrLevel++; e.ptrLevel > startDetectingCyclesAfter {
		// We're a large number of nested ptrEncoder.encode calls deep;
		// start checking if we've run into a pointer cycle.
		ptr := v.Interface()
		if _, ok := e.ptrSeen[ptr]; ok {
			e.error(&UnsupportedValueError{v, fmt.Sprintf("encountered a cycle via %s", v.Type())})
		}
		e.ptrSeen[ptr] = struct{}{}
		defer delete(e.ptrSeen, ptr)
	}
	pe.elemEnc(e, v.Elem())
	e.ptrLevel--
}

func newPtrEncoder(t reflect.Type) encoderFunc {
	enc := ptrEncoder{typeEncoder(t.Elem())}
	return enc.encode
}

type arrayEncoder struct {
	elemEnc encoderFunc
}

func (ae arrayEncoder) encode(e *encodeState, v reflect.Value) {
	e.WriteByte('[')
	n := v.Len()
	for i := 0; i < n; i++ {
		if i > 0 {
			e.WriteByte(',')
		}
		ae.elemEnc(e, v.Index(i))
	}
	e.WriteByte(']')
}

func newArrayEncoder(t reflect.Type) encoderFunc {
	enc := arrayEncoder{typeEncoder(t.Elem())}
	return enc.encode
}

